Skip to content

Conversation

@hazei-odoo
Copy link

No description provided.

@robodoo
Copy link

robodoo commented Oct 20, 2025

Pull request status dashboard

@Mathilde411 Mathilde411 self-requested a review October 20, 2025 11:56
@Mathilde411 Mathilde411 changed the title [ADD] estate: create the app. HAZEI Onboarding Oct 20, 2025
@Mathilde411 Mathilde411 self-assigned this Oct 20, 2025
Copy link

@Mathilde411 Mathilde411 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small things here :)

@@ -0,0 +1 @@
from . import models No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of file line is missing here

'summary' : 'Real Estate Management Tutorial',
'application': True,

} No newline at end of file

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

End of file line is missing here

@Mathilde411 Mathilde411 self-requested a review October 21, 2025 11:18
@Mathilde411 Mathilde411 removed their assignment Oct 21, 2025
Copy link

@Mathilde411 Mathilde411 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work here ! :D
I didn't highlight them all but be careful about the naming of the ids/files/names according to: https://www.odoo.com/documentation/19.0/contributing/development/coding_guidelines.html

name = fields.Char(string="Title", required=True)
description = fields.Text()
postcode = fields.Char()
date_availability = fields.Date(copy=False, readonly=True, default=date.today() + relativedelta(months=3))

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
date_availability = fields.Date(copy=False, readonly=True, default=date.today() + relativedelta(months=3))
date_availability = fields.Date(copy=False, readonly=True, default=lambda self: date.today() + relativedelta(months=3))

If you don't do this, it will only be evaluated one time and not work tomorrow on a long running instance.

string='garden_orientation',
selection=[('north', 'North'), ('south', 'South'),
('east', 'East'), ('west', 'West')])
active = fields.Boolean(default=False)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
active = fields.Boolean(default=False)
active = fields.Boolean()

False is already the default for Boolean

Copy link

@alan-odoo alan-odoo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @hazei-odoo 👋

As you asked, I checked your development. My comments are mainly about python conventions 👋.

Also do not forget to add a title and a description to your PR. The title must have the same structure than a commit title -> [ADD/IMP/FIX...] app: the purpose of the pr (must start by a verb). And for the description, it must mainly explain why the pr is doing this and not how.

Thanks for you work 😃!

Comment on lines 1 to 4
from odoo import models, fields, api
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo.exceptions import UserError

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports should also be sorted alphabetically. I would add that external imports (all imports “date...” for example) must be imported first, followed by internal imports (starting with “from odoo”).

_description = "Real Estate Property"

name = fields.Char(string="Title", required=True)
Property_Type = fields.Text()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be property_type. Variables use only lowercase letters. Uppercase letters are used by constants and classes.

Variable -> this_is_a_variable =
constant -> THIS_IS_A_CONSTANT
class -> ThisIsAClass

)

def action_set_sold(self):
if (self.state != "Cancelled"):

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need of the parenthesis. The != evaluate if the two elements are similar and will return a boolean value in any case.

Suggested change
if (self.state != "Cancelled"):
if self.state != "Cancelled":

Copy link

@Mathilde411 Mathilde411 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job !
Not much to say. Did like I would on a real review.

Comment on lines +40 to +46
selection=[
('new', 'New'),
('offer_received', 'Offer Received'),
('offer_accepted', 'Offer Accepted'),
('sold', 'Sold'),
('cancelled', 'Cancelled')]
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a constant, so it should go outside the class and be given a more consistent name like PROPERTY_STATES

Comment on lines +48 to +53
def action_set_sold(self):
if self.state != "cancelled":
self.state = "sold"
else:
raise UserError("A cancelled property can not be sold")
return True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_set_sold(self):
if self.state != "cancelled":
self.state = "sold"
else:
raise UserError("A cancelled property can not be sold")
return True
def action_set_sold(self):
if self.state == "cancelled":
raise UserError("A cancelled property can not be sold")
self.state = "sold"
return True

More concise and extendable

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what would happen here if self contains more than 1 property ?

Comment on lines +48 to +60
def action_set_sold(self):
if self.state != "cancelled":
self.state = "sold"
else:
raise UserError("A cancelled property can not be sold")
return True

def action_set_cancelled(self):
if (self.state != "sold"):
self.state = "cancelled"
else:
raise UserError("A sold property can not be cancelled")
return True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per attribute ordering (coding guidelines), this should go lower in the file

Comment on lines +55 to +60
def action_set_cancelled(self):
if (self.state != "sold"):
self.state = "cancelled"
else:
raise UserError("A sold property can not be cancelled")
return True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_set_cancelled(self):
if (self.state != "sold"):
self.state = "cancelled"
else:
raise UserError("A sold property can not be cancelled")
return True
def action_set_cancelled(self):
if self.state == "sold":
raise UserError("A sold property can not be cancelled")
self.state = "cancelled"
return True

No parentheses around if, and more concise

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what would happen here if self contains more than 1 property ?

@api.depends('living_area', 'garden_area')
def _compute_total_area(self):
for property in self:
property.total_area = property.living_area + (property.garden_area or 0)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
property.total_area = property.living_area + (property.garden_area or 0)
property.total_area = property.living_area + property.garden_area

garden_area is an integer, it cannot be False

Comment on lines +76 to +79
def _compute_best_price(self):
for record in self:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def _compute_best_price(self):
for record in self:
if record.offer_ids:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0
def _compute_best_price(self):
for record in self:
record.best_price = max(record.offer_ids.mapped('price')) if record.offer_ids else 0

First check not needed

Comment on lines +90 to +102
_check_expected_price = models.Constraint(
'CHECK(expected_price >= 0)', 'The expected price must be strictly positive.')

_check_selling_price = models.Constraint(
'CHECK(selling_price > 0)', 'The selling price must be positive.')

@api.constrains('selling_price', 'expected_price')
def _check_selling_price_expected_price(self):
for record in self:
if record.selling_price == 0:
continue
if float_compare(record.selling_price, 0.9 * record.expected_price, precision_digits=2) == -1:
raise UserError("The selling price must be at least 90% of the expected price.")

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As per attribute ordering this needs to be higher

Comment on lines +37 to +43
def action_refuse(self):
for offer in self:
if offer.state != 'accepted':
offer.state = 'refused'
else:
raise UserError("An accepted offer cannot be refused.")
return True

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def action_refuse(self):
for offer in self:
if offer.state != 'accepted':
offer.state = 'refused'
else:
raise UserError("An accepted offer cannot be refused.")
return True
def action_refuse(self):
if any(offer.state == 'accepted' for offer in self):
raise UserError("An accepted offer cannot be refused.")
self.state = 'refused'
return True

More concise

Comment on lines +17 to +19
],
'installable': True,
'application': True,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
],
'installable': True,
'application': True,
],
'application': True,

default value for installable is already True

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants